﻿/*
  Copyright (c) 2011, Mikheev Rostislav
  Copyright (c) 2011, FreshFarsh team

  Steel Engine source code is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
/**
  font.cpp

  @author Mikheev Rostislav <hacenator@gmail.com>
  @date 13.03.2011
  @brief GUI Font class source code
*/


// TODO(hacenator): BUFFER_MESSAGE_SIZE to const
#define BUFFER_MESSAGE_SIZE 1024

#include "gui/font.h"

namespace steel {

GUIFont::GUIFont() {
}

GUIFont::GUIFont(const char *fontfile, int size) {
        #define imagesize 512*512

  //LOG_INFO("Loading font from file '%s'", fontfile);

        _scale = 1;

        _color.r = 255;
        _color.g = 0;
        _color.b = 0;
        _color.a = 255;

  //wstring ch;


  // characters
  const std::wstring chars( L"abcdefghijklmnopqrstuvwxyz"
                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                        "абвгдеёжзийклмнопрстуфхцчшщьыъэюя"
                        "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"
                        "1234567890~!@#$%^&*()-=+;:"
                        "'\",./?[]|\\ <>`\u0000");

  // Margins around characters to prevent them from 'bleeding' into
  // each other.
  const int margin = 3;
  int image_height = 0, image_width = 256;

    // Init FreeType
  FT_Library library;
  if (FT_Init_FreeType(&library) != 0)
    throw RuntimeError() << "Can't load freetype library";

    // Loading fonts
  FT_Face face;
  if (FT_New_Face(library, fontfile, 0, &face))
    throw RuntimeError() << "FT_New_Face failed (there is probably a problem with your font file)";

  // Abort if this is not a 'true type', scalable font.
  if (!(face->face_flags & FT_FACE_FLAG_SCALABLE) or
    !(face->face_flags & FT_FACE_FLAG_HORIZONTAL))
    throw RuntimeError() << "Error setting font size.";

  // Set font size
  FT_Set_Pixel_Sizes(face, size, 0);

  // First we go over all the characters to find the max descent
  // and ascent (space required above and below the base of a
  // line of text) and needed image size. There are simpler methods
  // to obtain these with FreeType but they are unreliable.
  int max_descent = 0;
  int max_ascent = 0;
  int space_on_line = image_width - margin;
  int lines = 1;

  // max character code
  int max_charcode = 0;

  for (int i = 0; i < chars.size(); ++i) {
    // Look up the character in the font file.
    int char_index = FT_Get_Char_Index(face, static_cast<uint32_t>(chars[i]));
    if (chars[i] == L'\u0000')
      char_index = 0;

    // searching maximum chacter code
    max_charcode = std::max(static_cast<int>(chars[i]), max_charcode);

    // Render the current glyph.
    FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT);
    FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);

    int advance = (face->glyph->metrics.horiAdvance >> 6) + margin;

    // If the line is full go to the next line
    if (advance > space_on_line){
      space_on_line = image_width - margin;
      ++lines;
    }

    space_on_line -= advance;

    max_ascent = std::max(face->glyph->bitmap_top, max_ascent);
    max_descent = std::max(face->glyph->bitmap.rows - face->glyph->bitmap_top, max_descent);
  }

  // Compute how high the texture has to be.
  int needed_image_height = (max_ascent + max_descent + margin) * lines + margin;
  // Get the first power of two in which it fits.
  image_height = 16;
  while (image_height < needed_image_height)
    image_height *= 2;

  // line height
  _line_height = max_ascent + max_descent;
  _tex_line_height = static_cast<float>(_line_height) / image_height;

  // Allocate memory for the texture, and set it to 0
  uint8_t * image = new uint8_t[image_height * image_width];

  // fill 0
  /*for (int i = 0; i < image_height * image_width; ++i)
      image[i] = 0;*/
  MemZeroFill(&image[0], sizeof(uint8_t) * image_height * image_width);

  // Allocate space for the GlyphEntries
  _glyphs = new Glyph[chars.size()];
  _charmap = new int[max_charcode + 1];

  // fill charmap
  MemZeroFill(&_charmap[0], sizeof(int) * max_charcode);
  /*for (int i = 0; i <= max_charcode; i++) {
      _charmap[i] = 0;
  }*/

  // Drawing offset
  int x = margin;
  int y = margin + max_ascent;

  //
  // Draw font charcters to texture
  //
  for (int i = 0; i < chars.size(); ++i){
    int char_index = FT_Get_Char_Index(face, static_cast<uint32_t>(chars[i]));

    if (chars[i] == L'\u0000')
      char_index = 0;

    //jLog::infof("%d char %d\n", i, (int)chars[i]);

    // Render the glyph
    FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT);
    FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);

    // See whether the character fits on the current line
    int advance = (face->glyph->metrics.horiAdvance >> 6) + margin;
    if (advance > image_width - x){
      x = margin;
      y += (max_ascent + max_descent + margin);
    }

    // Fill in the GlyphEntry
    //entries[i].ascii = chars[i];
    _glyphs[i].width = advance - 3;
    _glyphs[i].x1 = static_cast<float>(x) / static_cast<float>(image_width);
    _glyphs[i].x2 = (static_cast<float>(x) + static_cast<float>(_glyphs[i].width)) / static_cast<float>(image_width);
    _glyphs[i].y = (static_cast<float>(y) - static_cast<float>(max_ascent)) / static_cast<float>(image_height);

    _charmap[static_cast<int>(chars[i])] = i;

    // Copy the image gotten from FreeType onto the texture
    // at the correct position
    for (int row = 0; row < face->glyph->bitmap.rows; ++row)
      for (int pixel = 0; pixel < face->glyph->bitmap.width; ++pixel)
        image[(x + face->glyph->bitmap_left + pixel) +
              (y - face->glyph->bitmap_top + row) * image_width] =
              face->glyph->bitmap.buffer[pixel + row * face->glyph->bitmap.pitch];

    x += advance;
  }

  //
  // Generate font texture
  //
  glGenTextures(1, &_texture);
  glBindTexture(GL_TEXTURE_2D, _texture);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, image_width, image_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, image);

  // Delete image
  delete[] image;

  // 1: Index buffer
  uint16_t index_array[4];
  index_array[0] = 0; index_array[1] = 1; index_array[2] = 2; index_array[3] = 3;
  glGenBuffers(1, &_index_buffer);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _index_buffer);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * 4, index_array, GL_STATIC_DRAW);

  // 2: Vertex buffer
  glGenBuffers(1, &_vertex_buffer);
  //glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
  //glBufferData(GL_ARRAY_BUFFER, sizeof(sCharacter) * 4, &_vertex_array[0].x, GL_STATIC_DRAW);

  // Unload Freetype
  FT_Done_Face(face);
  FT_Done_FreeType(library);
}

//-----------------------------------------------------------------------------

GUIFont::~GUIFont() {
  if (glIsBuffer(_vertex_buffer))
    glDeleteBuffers(1, &_vertex_buffer);

  if (glIsBuffer(_index_buffer))
    glDeleteBuffers(1, &_index_buffer);

  // delete texture
  if (glIsTexture(_texture))
    glDeleteTextures(1, &_texture);

  // clear glyph data
  delete [] _glyphs;
  delete [] _charmap;
}

//-----------------------------------------------------------------------------

void GUIFont::DrawTexture() {
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, _texture);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glColor3f(1, 0, 0);

  glBegin(GL_QUADS);

  glTexCoord2f(0, 0);
  glVertex2f(0, 0);
  glTexCoord2f(0, 1);
  glVertex2f(0, 600);
  glTexCoord2f(1, 1);
  glVertex2f(600, 600);
  glTexCoord2f(1, 0);
  glVertex2f(600, 0);

  glEnd();
}

//-----------------------------------------------------------------------------

void GUIFont::PrintChar(int x, int y, const wchar_t * p) {
  float tex_x, tex_y;

  // character quad
  Vertex2D vertex_array[4];

  // get glyph unicode
  Glyph * glyph = &_glyphs[_charmap[static_cast<int>(*p)]];

  // Create vertex array with point and texture coordinates
  vertex_array[0].point.x = x;
  vertex_array[0].point.y = y;
  vertex_array[0].texture.x = glyph->x1;
  vertex_array[0].texture.y = glyph->y;

  vertex_array[1].point.x = x;
  vertex_array[1].point.y = y + _line_height;
  vertex_array[1].texture.x = glyph->x1;
  vertex_array[1].texture.y = glyph->y + _tex_line_height;

  vertex_array[2].point.x = x + glyph->width;
  vertex_array[2].point.y = y + _line_height;
  vertex_array[2].texture.x = glyph->x2;
  vertex_array[2].texture.y = glyph->y + _tex_line_height;

  vertex_array[3].point.x = x + glyph->width;
  vertex_array[3].point.y = y;
  vertex_array[3].texture.x = glyph->x2;
  vertex_array[3].texture.y = glyph->y;

  // Enable alpha blending
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // Point coordinates
  glBindBuffer(GL_ARRAY_BUFFER, _vertex_buffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex2D) * 4, vertex_array, GL_STATIC_DRAW);
  glVertexPointer(2, GL_FLOAT, sizeof(Vertex2D), STRUCT_OFFSET_2D_POINT );

  // Texture coordinates
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, _texture);
  glActiveTexture(GL_TEXTURE0);
  glClientActiveTexture(GL_TEXTURE0);
  glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex2D), STRUCT_OFFSET_2D_TEXTURE );

  // Color
  glColor4ubv(&_color.v[0]);

  // Draw
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _index_buffer);
  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, NULL);

  // Unbind
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

//-----------------------------------------------------------------------------

void GUIFont::PrintInRect(int x, int y, const char * string, Rect rect) {
/*      if (string == NULL)
                return;

        char * p;

        UIRect charRect;

        float offset = 0;

        p = string;

        glColor4f(0,0,1,0.6);
        #ifdef DEBUG
        DrawRect((UIRect)rect, GL_LINE_LOOP);
        #endif

        glEnable (GL_SCISSOR_TEST);

        glScissor(rect.left,
                        MorfCore::getInstance().GetScreenHeight() - (rect.bottom),
                        rect.right - rect.left,
                        rect.bottom - rect.top);

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        while (*p) {
                // рисуем только то что будет видно в диапозоне Rect

                charRect.left = x + offset;
                charRect.right = x + offset + _scale * _charlen[(ubyte)*p];
                charRect.top = y;
                charRect.bottom = y + _scale * 32;

                if (RectRectIntersection(rect, charRect)) {
                        printChar(x + offset, y, p);
                }

                offset += _scale * _charlen[(ubyte)*p];
                p++;
        }

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glDisable (GL_SCISSOR_TEST);*/
}

//-----------------------------------------------------------------------------

void GUIFont::PrintMultilineInRect(int x, int y, const char *string, Rect rect, const Rect *textRect) {
        /*if (string == NULL)
                return;

        char * p;

        UIRect charRect;

        float offsetX = 0;
        float offsetY = 0;

        p = string;

        glEnable (GL_SCISSOR_TEST);

        glScissor(rect.left,
                        MorfCore::getInstance().GetScreenHeight() - (rect.bottom),
                        rect.right - rect.left,
                        rect.bottom - rect.top);

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        while (*p) {
                // рисуем только то что будет видно в диапозоне Rect

                charRect.left = x + offsetX;
                charRect.right = x + offsetX + _scale * _charlen[(ubyte)*p];
                charRect.top = y + offsetY;
                charRect.bottom = y + offsetY + _scale * 32;

                // если \n или overflow переходим на другую строку
                if ((charRect.right > rect.right) || (*p == '\n'))  {
                        offsetY += _scale * 32;
                        offsetX = 0;
                }

                if (RectRectIntersection(rect, charRect)) {
                        printChar(x + offsetX, y + offsetY, p);
                }

                offsetX += _scale * _charlen[(ubyte)*p];
                p++;
        }

        //
        if (textRect != NULL) {
                textRect->left = rect.left;
                textRect->top = rect.top;
                textRect->right = (offsetY == 0 ? offsetX : rect.right );
                textRect->bottom = rect.top + (offsetY == 0 ? _scale * 32 : offsetY + _scale * 32 );
        }

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glDisable (GL_SCISSOR_TEST);*/
}

//-----------------------------------------------------------------------------

void GUIFont::Print(int x, int y, const wchar_t *string) {
        if (string == NULL)
                return;

        wchar_t *p;

        float offset = 0;

        p = const_cast<wchar_t *>(string);

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        while (*p) {
                PrintChar(x + offset, y, p);

                offset += _glyphs[_charmap[(int) *p]].width;
                //offset += 10;

                p++;
        }

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}

//-----------------------------------------------------------------------------

void GUIFont::Printf(int x, int y, const wchar_t * format, ...) {
        va_list ap;

        wchar_t buffer[BUFFER_MESSAGE_SIZE] = L"";

        if (format != L"") {
                va_start(ap, format);
#ifdef _WIN32
                vswprintf(buffer, format, ap);
#elif __linux__
                vswprintf(buffer, BUFFER_MESSAGE_SIZE, format, ap);
#endif
                va_end(ap);

                Print(x, y, buffer);
        }
}

//-----------------------------------------------------------------------------

float GUIFont::TextHeight() {
  return _line_height;
}

//-----------------------------------------------------------------------------

float GUIFont::TextWidth(const wchar_t * format, ...) {
  float width = 0;
  int i = 0;

  wchar_t text[BUFFER_MESSAGE_SIZE] = L"";
  va_list ap;

  if (format == NULL)
    return 0;

  va_start(ap, format);
#ifdef _WIN32
  vswprintf(text, format, ap);
#elif __linux__
  vswprintf(text, BUFFER_MESSAGE_SIZE, format, ap);
#endif
  va_end(ap);

  while (text[i]) {
    width += _glyphs[_charmap[static_cast<int>(text[i])]].width;
    i++;
  }

  return width;
}

}
